package ms.sudoku.technique;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import ms.sudoku.PuzzleState;
import ms.sudoku.util.IntSet;

import org.lex.game.sudoku.model.Point;

public final class HiddenSingleTechnique extends EliminationTechnique {
	public HiddenSingleTechnique() {
	}

	public long getDifficultyLevel() {
		return 3;
	}

	/**
	 * Runs this elimination technique over the supplied puzzle state and
	 * previously computed possible numbers.<br>
	 * Returns Whether more changes may be possible based on changes made during
	 * this execution.
	 */
	public boolean execute(PuzzleState state, boolean exitEarlyWhenSoleFound, IntSet[][] possibleNumbers,
			AtomicInteger numberOfChanges, AtomicBoolean exitedEarly) {
		numberOfChanges.set(0);
		exitedEarly.set(false);

		int gridSize = state.getGridSize();
		int boxSize = state.getBoxSize();

		// For each number that can exist in the puzzle (0-8, etc.)
		for (int n = 0; n < gridSize; n++) {
			/*
			 * For each row, if number only exists as a possibility in one cell,
			 * set it.
			 */
			for (int row = 0; row < gridSize; row++) {
				int seenIndex = Integer.MIN_VALUE;
				for (int col = 0; col < gridSize; col++) {
					if (possibleNumbers[row][col].contains(n)) {
						// If this is the first time you locate the number, set
						// seenIndex.
						if (Integer.MIN_VALUE == seenIndex)
							// You have seen this number before, so move on
							seenIndex = col;
						else {
							seenIndex = Integer.MIN_VALUE;
							break;
						}
					}

				}
				if (Integer.MIN_VALUE != seenIndex && possibleNumbers[row][seenIndex].size() > 1) {
					possibleNumbers[row][seenIndex].clear();
					possibleNumbers[row][seenIndex].add(n);
					numberOfChanges.incrementAndGet();
					;
					if (exitEarlyWhenSoleFound) {
						exitedEarly.set(true);
						return false;
					}

				}

			}

			/*
			 * For each column, if number only exists as a possibility in one
			 * cell, set it. Same basic logic as above.
			 */
			for (int col = 0; col < gridSize; col++) {
				int seenIndex = Integer.MIN_VALUE;
				for (int row = 0; row < gridSize; row++) {
					if (possibleNumbers[row][col].contains(n)) {
						if (Integer.MIN_VALUE == seenIndex)
							seenIndex = row;
						else {
							seenIndex = Integer.MIN_VALUE;
							break;
						}
					}

				}
				if (Integer.MIN_VALUE != seenIndex && possibleNumbers[seenIndex][col].size() > 1) {
					possibleNumbers[seenIndex][col].clear();
					possibleNumbers[seenIndex][col].add(n);
					numberOfChanges.incrementAndGet();
					if (exitEarlyWhenSoleFound) {
						exitedEarly.set(true);
						return false;
					}

				}

			}

			// For each grid, if number only exists as a possibility in one
			// cell, set it.
			// Same basic logic as above.
			for (int gridNum = 0; gridNum < gridSize; gridNum++) {
				int gridX = gridNum % boxSize;
				int gridY = gridNum / boxSize;
				int startX = gridX * boxSize;
				int startY = gridY * boxSize;
				boolean canEliminate = true;
				Point seenIndex = null;
				for (int x = startX; x < startX + boxSize && canEliminate; x++) {
					for (int y = startY; y < startY + boxSize; y++) {
						if (possibleNumbers[x][y].contains(n)) {
							if (null == seenIndex)
								seenIndex = new Point(x, y);
							else {
								canEliminate = false;
								seenIndex = null;
								break;
							}
						}

					}
				}
				if (null != seenIndex && canEliminate && possibleNumbers[seenIndex.x][seenIndex.y].size() > 1) {
					possibleNumbers[seenIndex.x][seenIndex.y].clear();
					possibleNumbers[seenIndex.x][seenIndex.y].add(n);
					numberOfChanges.incrementAndGet();
					if (exitEarlyWhenSoleFound) {
						exitedEarly.set(true);
						return false;
					}

				}

			}
		}
		return numberOfChanges.get() != 0;
	}
}
